import {
  Action,
  fromJwt,
  fromParam,
  JwtPayload,
  JwtPayloadDto,
  Permission,
  PermissionsDecoratorOptions,
  PermissionsTo,
} from '@boticord/auth'
import { Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post } from '@nestjs/common'
import {
  CacheUserUpdateDto,
  CrutchesDto,
  UpdateUserDto,
  User, UserWebhook,
} from '@boticord/users-client'
import { UsersService } from '../services/users.service'
import { ErrorCodes } from '@boticord/consts'
import { BotClientService } from '@boticord/bot-client'
import { AnalyseRequest, AnalyticsOptions, EventTypes } from '@boticord/analytics'
import { FindOptionsWhere, Repository } from 'typeorm'
import { Bot, PendingBot, ResourceStatus, Server } from '@boticord/resources-client'
import { InjectRepository } from '@nestjs/typeorm'
import { BitFieldUtil } from '@boticord/common'

const analyticsOptions: AnalyticsOptions = {
  affected: fromParam('id'),
  user: fromJwt('id'),
}

const permissionsOptions: PermissionsDecoratorOptions<User> = {
  fetch: {
    id: fromParam('id'),
    exception: new NotFoundException({ code: ErrorCodes.UnknownUser }),
  },
}

@Controller()
export class UsersController {
  constructor(
    private readonly users: UsersService,
    private readonly bot: BotClientService,
    @InjectRepository(Bot) private readonly bots: Repository<Bot>,
    @InjectRepository(PendingBot) private readonly pendingBots: Repository<PendingBot>,
    @InjectRepository(Server) private readonly servers: Repository<Server>,
  ) {}

  /** get user data */
  @Get(':id')
  @AnalyseRequest(EventTypes.ProfileViewed, analyticsOptions)
  async getUser(@Param('id') userID: string, @JwtPayload() jwt?: JwtPayloadDto) {
    const usr = await this.users.get(userID)
    if (!usr) throw new NotFoundException({ code: ErrorCodes.UnknownUser })

    const authorized = jwt?.id === usr.id
      || BitFieldUtil.has(jwt?.permissions ?? 0, Permission.ModerateBots)
      || BitFieldUtil.has(jwt?.permissions ?? 0, Permission.ModerateServers)

    const query: FindOptionsWhere<Server | Bot> = { owner: usr.id }

    if (!authorized) {
      query.status = ResourceStatus.Public
    } else {
      usr.webhook = (await this.users.getWebhook(userID)) as UserWebhook | undefined
    }

    const servers = await this.servers.find({ where: query as FindOptionsWhere<Server> })
    const bots = await this.bots.find({ where: query as FindOptionsWhere<Bot> })
    let pendingBots: PendingBot[] = []
    if (authorized) {
      pendingBots = await this.pendingBots.find({ where: { status: ResourceStatus.Pending, owner: usr.id } })
    }

    return { ...usr, servers, bots, pendingBots }
  }

  @Post('/crutches')
  @AnalyseRequest(EventTypes.MassiveProfilesViewed, analyticsOptions)
  async getCrutches(@Body() body: CrutchesDto) {
    return this.users.getMany(body.ids)
  }

  @Get(':id/discord')
  @PermissionsTo(Action.ReadApplication, User)
  @AnalyseRequest(EventTypes.DiscordUserViewed, analyticsOptions)
  async getDiscordUser(@Param('id') userID: string) {
    const user = await this.bot.getUser(userID)
    if (!user) throw new NotFoundException({ code: ErrorCodes.UnknownUser })

    const result: CacheUserUpdateDto = { ...user, id: userID }

    await this.users.updateUserDBCache(result)

    return result
  }

  /** edit user data */
  @Patch(':id')
  @PermissionsTo(Action.Modify, User, permissionsOptions)
  @AnalyseRequest(EventTypes.ProfileEdited, analyticsOptions)
  async patchUser(
    @Param('id') id: string,
    @Body() update: UpdateUserDto,
  ) {
    return this.users.update(id, update)
  }

  /** get user by short domain */
  @Get('short/:short')
  async getUserByShort(@Param('short') short: string) {
    const usr = await this.users.getByShort(short)
    if (!usr) throw new NotFoundException({ code: ErrorCodes.UnknownShortDomain })
    return usr
  }

  @Post(':id/automation/auth')
  @PermissionsTo(Action.Modify, User, permissionsOptions)
  @AnalyseRequest(EventTypes.AutomationAuthorized, analyticsOptions)
  async authorize(@Param('id') id: string) {
    return await this.users.auth.makeAutomationToken(id)
  }

  @Delete(':id/automation/auth')
  @PermissionsTo(Action.Modify, User, permissionsOptions)
  @AnalyseRequest(EventTypes.AutomationUnauthorized, analyticsOptions)
  async revoke(@Param('id') id: string) {
    return await this.users.auth.revokeAutomationToken(id)
  }

  @Get(':id/automation/auth')
  @PermissionsTo(Action.Modify, User, permissionsOptions)
  @AnalyseRequest(EventTypes.AutomationAuthViewed, analyticsOptions)
  async getAuth(@Param('id') id: string) {
    return await this.users.auth.getSignedAutomationToken(id)
  }
}
